#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include "LockGuard.hpp"

#define NUM 4

// 之后直接使用全局的lock。全局的锁不需要调用destory。或者局部变量定义成static也是可以
// pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// 对一个全局变量进行多线程并发--/++操作是否是安全的？不是安全的
// 不管你怎么定义这个全局变量，他一定是在内存中的。初始值设置为1000
// 如果要对全局变量做--操作，本质是对数据做计算。在计算机中，要对数据做计算，必须要在cpu内部做计算。
// 因此第一步先将全局变量读入到cpu的寄存器中。
// 2、在cpu内部做--计算，而--操作会影响自己，所以操作完后还要把计算结果写回到内存。
// 3、将数据写回到内存
// 这三个步骤，每一条都会对应一条汇编操作
int tickets = 3000; // 用多线程模拟抢票

class threadData
{
public:
    threadData(int number, pthread_mutex_t *lock)
    {
        thread_name = "thread-" + std::to_string(number);
        _lock = lock;
    }

    std::string thread_name;
    pthread_mutex_t *_lock;
};

// 加锁的本质：是用时间换取安全
// 加锁的表现：线程对于临界区代码串行执行
// 加锁的原则：尽量的保证临界区代码越少越好！

// 纯互斥环境，如果锁资源分配不够合理，容易导致其他线程的饥饿问题。不是说只要有互斥，必有饥饿。
// void * getTickets(void* args)
// {
//     threadData* td = static_cast<threadData*>(args);
//     const char* name = td->thread_name.c_str();
//     // 逻辑计算和数值计算都要从内存加载到cpu当中，从而导致数据不一致。
//     // 解决方式（互斥）：对共享数据的任何访问，保证任何时候只有一个执行流访问。  --- 互斥（锁）
//     while(true)
//     {
//         // 线程对于锁的竞争能力可能会不同 -- 有例子
//         pthread_mutex_lock(td->_lock);  // 申请锁成功才能往后执行，不成功，在这里阻塞。锁本身就是共享资源。所以，申请和释放锁本身就被设计成了原子的
//         // 逻辑计算
//         if(tickets > 0)
//         {
//             usleep(1000);  // 休眠1000微妙，模拟抢票花费时间
//             printf("who=%s, get a ticket: %d\n", name, tickets);  //?
//             // 数值计算
//             tickets--;
//             pthread_mutex_unlock(td->_lock);
//         }
//         else
//         {
//             pthread_mutex_unlock(td->_lock);
//             break;
//         }
//         usleep(14);  // 没加sleep的时候会发现某一个线程长期占用锁，这是正常的。因为一释放锁它又去申请锁。而加了sleep是先别申请锁，也就是增加了其他线程竞争锁的能力
//         // 但是代码逻辑不符合正常，我们抢到了票，会立马去抢下一张吗？不会。还要执行抢到票之后的后续动作。用usleep模拟
//     }
//     printf("%s ... quit\n", name);
//     return nullptr;
// }

void *getTickets(void *args)
{
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->thread_name.c_str();
    // 逻辑计算和数值计算都要从内存加载到cpu当中，从而导致数据不一致。
    // 解决方式（互斥）：对共享数据的任何访问，保证任何时候只有一个执行流访问。  --- 互斥（锁）
    while (true)
    {
        {
            LockGuard lockguard(td->_lock); // 临时的LockGuard对象，用对象的声明周期管理加锁。RAII风格
            // 逻辑计算
            if (tickets > 0)
            {
                usleep(1000);                                        // 休眠1000微妙，模拟抢票花费时间
                printf("who=%s, get a ticket: %d\n", name, tickets); // 
                // 数值计算
                tickets--;
            }
            else
                break;
        }
        usleep(14); // 没加sleep的时候会发现某一个线程长期占用锁，这是正常的。因为一释放锁它又去申请锁。而加了sleep是先别申请锁，也就是增加了其他线程竞争锁的能力
        // 但是代码逻辑不符合正常，我们抢到了票，会立马去抢下一张吗？不会。还要执行抢到票之后的后续动作。用usleep模拟
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{
    // pthread_mutex_t也是一种数据类型,存放在主线程中
    pthread_mutex_t lock;
    pthread_mutex_init(&lock, nullptr);

    // 记录所有线程的id
    std::vector<pthread_t> tids;
    // 记录所有线程的数据
    std::vector<threadData *> thread_datas;
    for (int i = 1; i <= NUM; ++i)
    {
        pthread_t tid;
        threadData *td = new threadData(i, &lock);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTickets, thread_datas[i - 1]);
        tids.push_back(tid);
    }

    for (int i = 0; i < NUM; ++i)
    {
        pthread_join(tids[i], nullptr);
    }

    for (auto td : thread_datas)
    {
        delete td;
    }

    pthread_mutex_destroy(&lock);
    return 0;
}

// 线程切换的时间点是内核到用户返回的时候在做切换检测。但是不一定会切换
// 而sleep的时候，1、既可以保证当前线程被挂起或被唤醒，所以一定会经历从内核到用户的状态切换。
// 2、sleep的过程中，尽量的让当前线程的时间片雷同

// 1、外面来的，必须排队
// 2、出来的人，不能立马重新申请锁，必须排列到队列的尾部。
// 满足1和2，会让所有线程获取锁按照一定的顺序了。
// 按照一定的顺序性获取资源 -- 同步

// 在临界区中，线程可以被切换吗？可以切换！
// 在线程被切换出去的时候，是持有锁被切换走的。因此我不在期间，照样没有人能进入临界区访问临界资源。
// 当前持有锁的线程对于外变的人来讲，只有两种状态是有意义的，一种是我持有锁，二是释放锁。
// 换句话说，当前线程在访问临界资源的时候，其他线程知道自己压根就没机会，他们也不会关心你在执行代码的中间过程了。
// 其他线程在关注当前线程执行时，只关心你有没有释放锁，有没有申请锁。
// 对于其他线程来讲，一个线程要么没有锁，要么释放锁，当前线程访问临界区的过程，对其他线程是原子的。

// 锁的原理
// 为什么ticket--不是原子的？编译器编译的时候会形成3条汇编语句
// 原子：一条汇编就是原子的。
// swap和exchange指令：作用是把寄存器和内存单元的数据相交换，由于只有一条指令，保证了原子性。

// 锁的原理
// pthread_mutex_lock函数对应的汇编
// lock:
//     movb $0, %al                          // 锁就是一个简单的变量，cpu内部有存器al。这条语句是把寄存器清零
//     xchgb %al, mutex                      // 把寄存器和内存中的锁进行交换。就是我们申请锁的过程
//     if(al寄存器的内容 > 0)                 // 判断
//         return 0                          // 申请锁成功
//     else
//         挂起等待                           // 申请锁失败
//     goto lock;

// // 交换的本质：
// // 把内存中的数据（共享），交换到cpu的寄存器（把数据交换到线程的硬件上下文中）
// // 把一个共享的锁，让一个线程以一条汇编的方式，交换到当前正在执行线程的上下文中。结果：当前线程持有锁

// unlock:
//     movb $1, mutex
//     唤醒等待mutex的线程;
//     return 0;